package com.lancoo.cvas.arrangecourse.coursescheduling;


import com.lancoo.cvas.arrangecourse.coursescheduling.campusscheduling.CampusScheduling;
import com.lancoo.cvas.arrangecourse.dataprocess.GenerateTimeslotRoom;
import com.lancoo.cvas.arrangecourse.dataprocess.LinkProcess;
import com.lancoo.cvas.arrangecourse.dataprocess.TeacherAndCourseProhibitProcess;
import com.lancoo.cvas.arrangecourse.entities.*;
import com.lancoo.cvas.arrangecourse.rulecheck.RuleInit;
import com.lancoo.cvas.pojo.dto.*;
import com.lancoo.cvas.service.ArrangeProcessService;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * @Author: liufeng
 * @CreateTime: 2023-12-12  20:56
 * @Description:
 */
@Slf4j
public class SchoolScheduling {
    /**
     * 开始排课
     *
     * @param taskId                学期排课计划id
     * @param teachingClassList     教学班信息列表（以授课类型为粒度）
     * @param roomList              教室信息列表
     * @param timeslotList          课时信息列表
     * @param roomProhibitList      教室禁排信息列表
     * @param classHourMap          锁定的排课单元信息列表
     * @param teacherRuleList       教师规则列表
     * @param courseRuleList        课程排课规则信息列表
     * @param classRuleList         课程排课规则信息列表
     * @param teachingClassRuleList 课程排课规则信息列表
     * @param baseRule              排课规则
     * @param weekNum               学期总周数
     * @return
     */
    public ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> start(Integer taskId,
                                                                            List<ArrangeTeachingClassDto> teachingClassList,
                                                                            List<ArrangeRoomDto> roomList,
                                                                            List<ArrangePeriod> timeslotList,
                                                                            List<ArrangeRoomRuleDto> roomProhibitList,
                                                                            ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                                                            List<ArrangeTeacherRuleDto> teacherRuleList,
                                                                            List<ArrangeCourseRuleDto> courseRuleList,
                                                                            List<ArrangeClassRuleDto> classRuleList,
                                                                            List<ArrangeTeachingClassRuleDto> teachingClassRuleList,
                                                                            BaseRule baseRule,
                                                                            Long weekNum,
                                                                            ArrangeProcessService arrangeProcessService) {
        //处理环节与课程，周次冲突的问题，并将环节，将教学班信息中拆分出去
        List<ArrangeTeachingClassDto> linkTeachingClassList = new LinkProcess().getResult(teachingClassList);
        log.info("#SchoolScheduling.start#--环节教学班数据分拣完成，"
                + "环节数量：" + (linkTeachingClassList != null ? linkTeachingClassList.size() : 0));

        //将禁排的教师和课程，在对应的教学班上面做好标记；
        //新增 行政班 教学班 禁排
        teachingClassList = new TeacherAndCourseProhibitProcess().getResult(teachingClassList,
                courseRuleList, teacherRuleList, classRuleList, teachingClassRuleList);
        log.info("#SchoolScheduling.start#--教师和课程禁排数据，在教学班上标记完毕");

        //合并教室和课时信息
        LinkedList<TimeslotRoom> timeslotRoomList = new GenerateTimeslotRoom().getResult(roomList,
                timeslotList, roomProhibitList, classHourMap, weekNum);
        log.info("#SchoolScheduling.start#--教室课时分组信息，生成完毕，"
                + "生成教室课时分组数量：" + (timeslotRoomList != null ? timeslotRoomList.size() : 0));

        //定义初始化排课规则的对象
        RuleInit ruleInit = new RuleInit(baseRule);
        //定义用来存储每次排课后。剩余未处理的排课教学班单元列表信息
        LinkedList<TeachingClassUnit> leaveList = new LinkedList<>();
        log.info("#SchoolScheduling.start#--基础规则初始化完毕");

        //开始进行无冲突排课
        leaveList = noClashScheduling(taskId, ruleInit, teachingClassList, timeslotRoomList, classHourMap,
                teacherRuleList, courseRuleList, leaveList, teachingClassRuleList,arrangeProcessService);

        //如果开启了跨校区排课，并且剩余了排课列表信息，那么需要进行一轮跨校区排课
        if (leaveList != null && !leaveList.isEmpty()
                && baseRule.getCrossCampusOption() == 1) {
            log.info("#SchoolScheduling.start#--开始进行跨校区排课");
            //开始进行跨校区排课
            leaveList = new CrossCampusScheduling(ruleInit).start(taskId, leaveList, timeslotRoomList, classHourMap);
            log.info("#SchoolScheduling.start#--跨校区排课结束，"
                    + "剩余未排课教学班排课单元数量：" + (leaveList != null ? leaveList.size() : 0));
        }

        /*
        开始进行冲突排课
         */
//        leaveList = clashScheduling(taskId, ruleInit, timeslotRoomList, classHourMap, leaveList);

        //生成未分配的教学班的课表信息
        if (leaveList != null && !leaveList.isEmpty()) {
            log.info("#SchoolScheduling.start#--剩余的未排课教学班排课单元数量：" + (leaveList != null ? leaveList.size() : 0)
                    + "，开始生成未排课的课表信息");
            //剩余的教学班排课单元，作为未分配（即：待排），添加到课表map中
            classHourMap = generateClassHourWithNoTimeslotRoom(leaveList, classHourMap);

            log.info("#SchoolScheduling.start#--生成未排课的课表信息--完成");
        }

        log.info("#SchoolScheduling.start#--排课完成");
//        //获取spring托管的，初始化计算单元对象
//        ClashesProcess clashesProcess = SpringUtil.getBean(ClashesProcess.class);
//        StopWatch stopWatch = new StopWatch();
//        stopWatch.start("计算冲突数");
//        int clashes = clashesProcess.calcClashes(classHourMap);
//        stopWatch.stop();
//        log.info("计算冲突数:" + clashes + ", 耗时："+ stopWatch.getLastTaskTimeMillis());
        return classHourMap;
    }

    /**
     * 开始无冲突排课
     *
     * @param taskId                排课选课计划id
     * @param ruleInit              排课规则初始化处理对象
     * @param teachingClassList     排课教学班
     * @param timeslotRoomList      教室课时分组
     * @param classHourMap          已排的正常课程，转换过来的排课单元map对象，key为timeslotCode
     * @param teacherRuleList       教师规则列表
     * @param courseRuleList        课程排课规则信息列表
     * @param leaveList             剩余的排课教学班排课单元列表
     * @param teachingClassRuleList 教学班固排规则
     * @return
     */
    private LinkedList<TeachingClassUnit> noClashScheduling(Integer taskId,
                                                            RuleInit ruleInit,
                                                            List<ArrangeTeachingClassDto> teachingClassList,
                                                            LinkedList<TimeslotRoom> timeslotRoomList,
                                                            ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                                            List<ArrangeTeacherRuleDto> teacherRuleList,
                                                            List<ArrangeCourseRuleDto> courseRuleList,
                                                            LinkedList<TeachingClassUnit> leaveList,
                                                            List<ArrangeTeachingClassRuleDto> teachingClassRuleList,
                                                            ArrangeProcessService arrangeProcessService) {
        log.info("#SchoolScheduling.noClashScheduling#--开始进行各校区无冲突排课");
        //将排课教学班列表，按照校区进行分组，key为校区id
        Map<String, LinkedList<ArrangeTeachingClassDto>> campusClassMap = teachingClassList.stream()
                .collect(Collectors.groupingBy(ArrangeTeachingClassDto::getCampusId,
                        Collectors.toCollection(LinkedList::new)));
        //将待分配的教室课时分组信息列表，按照校区进行分组，key为校区id
        Map<String, LinkedList<TimeslotRoom>> campusRoomMap = timeslotRoomList.stream()
                .collect(Collectors.groupingBy(TimeslotRoom::getCampusId, Collectors.toCollection(LinkedList::new)));

        //定义返回剩余的列表数据
        LinkedList<TeachingClassUnit> noclashLeaveList = new LinkedList<>();

        //遍历map，进行各校区独立排课
        for (Map.Entry<String, LinkedList<ArrangeTeachingClassDto>> entry : campusClassMap.entrySet()) {
            //获取校区id
            String campusId = entry.getKey();
            //获取指定校区的教学班列表
            LinkedList<ArrangeTeachingClassDto> campusClassList = entry.getValue();
            //开始各校区排课
            LinkedList<TeachingClassUnit> retList = new CampusScheduling(ruleInit).start(taskId, campusId,
                    campusClassList, campusRoomMap.get(campusId), classHourMap,
                    teacherRuleList, courseRuleList, leaveList, teachingClassRuleList,
                    arrangeProcessService);
            //如果剩余了未排课的排课单元
            if (retList != null && !retList.isEmpty()) {
                //添加到剩余的列表，放到后续进行处理
                noclashLeaveList.addAll(retList);
            }
        }

        log.info("#SchoolScheduling.noClashScheduling#--各校区无冲突自动排课结束，"
                + "剩余未排课教学班排课单元数量：" + (leaveList != null ? leaveList.size() : 0)
                + "剩余未分配的教室课时分组数量：" + (timeslotRoomList != null ? timeslotRoomList.size() : 0));
        //将已经分配的timeslotRoom对象，从源头列表中删除
        timeslotRoomList = removeAllocatedList(timeslotRoomList, campusRoomMap);
        campusClassMap.clear();
        campusClassMap = null;
        campusRoomMap.clear();
        campusRoomMap = null;

        return noclashLeaveList;
    }

    /**
     * 开始冲突排课
     * @param taskId 排课选课计划id
     * @param ruleInit 排课规则初始化处理对象
     * @param timeslotRoomList 教室课时分组
     * @param classHourMap 已排的正常课程，转换过来的排课单元map对象，key为timeslotCode
     * @param leaveList 剩余的排课教学班排课单元列表
     * @return
     */
    private LinkedList<TeachingClassUnit> clashScheduling(Integer taskId,
                                                          RuleInit ruleInit,
                                                          LinkedList<TimeslotRoom> timeslotRoomList,
                                                          ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap,
                                                          LinkedList<TeachingClassUnit> leaveList) {
        //开始冲突排课
        //如果剩余了排课列表信息，那么需要进行一轮冲突排课
        if (leaveList != null && !leaveList.isEmpty()) {
            log.info("#SchoolScheduling.clashScheduling#--开始进行冲突排课");

            //将剩余的排课教学班排课单元列表，按照校区进行分组，key为校区id
            Map<String, LinkedList<TeachingClassUnit>> leaveUnitMap = leaveList.stream()
                    .collect(Collectors.groupingBy(TeachingClassUnit::getCampusId,
                            Collectors.toCollection(LinkedList::new)));
            //将无冲突排课，剩余的教室课时分组信息列表，按照校区进行分组，key为校区id
            Map<String, LinkedList<TimeslotRoom>> leaveRoomMap = timeslotRoomList.stream()
                    .collect(Collectors.groupingBy(TimeslotRoom::getCampusId, Collectors.toCollection(LinkedList::new)));

            //遍历map，进行各校区独立冲突排课
            for (Map.Entry<String, LinkedList<TeachingClassUnit>> entry : leaveUnitMap.entrySet()) {
                //获取校区id
                String campusId = entry.getKey();
                //获取指定校区的教学班列表
                LinkedList<TeachingClassUnit> leaveUnitList = entry.getValue();

                //开始进行冲突排课
                LinkedList<TeachingClassUnit> retList = new ClashScheduling(ruleInit).start(taskId, campusId, leaveUnitList,
                        leaveRoomMap.get(campusId), classHourMap);

                //如果剩余了未排课的排课单元
                if (retList != null && !retList.isEmpty()) {
                    //添加到剩余的列表，放到后续进行处理
                    leaveList.addAll(retList);
                }
            }

            leaveUnitMap.clear();
            leaveUnitMap = null;
            leaveRoomMap.clear();
            leaveRoomMap = null;

            log.info("#SchoolScheduling.clashScheduling#--冲突排课结束，"
                    + "剩余未排课教学班排课单元数量：" + (leaveList != null ? leaveList.size() : 0)
                    + ", 剩余未分配的教室课时分组数量：" + (timeslotRoomList != null ? timeslotRoomList.size() : 0));
        }

        return leaveList;
    }

    /**
     * 将已经分配的timeslotRoom对象，从源头列表中删除
     *
     * @param allTimeslotRoomList 源头列表
     * @param campusRoomMap       剩余的未分配完的map
     * @return
     */
    private LinkedList<TimeslotRoom> removeAllocatedList(LinkedList<TimeslotRoom> allTimeslotRoomList,
                                                         Map<String, LinkedList<TimeslotRoom>> campusRoomMap) {

        /*
        //        StopWatch stopWatch = new StopWatch();
//        stopWatch.start("测试删除需要的时间");
        if (campusRoomMap != null && !campusRoomMap.isEmpty()) {
            //将剩余的可排课的map，转换成列表
            LinkedList<TimeslotRoom> nowLeaveList = new LinkedList<>(
                    campusRoomMap.values().stream()
                            .flatMap(Collection::stream)
                            .collect(Collectors.toList())
            );
            //建立源头列表的副本
            LinkedList<TimeslotRoom> tempList = new LinkedList<>(allTimeslotRoomList);
            //将当前剩余的列表，从副本中删除，剩下的就是需要从源头中删除的
            tempList.removeAll(nowLeaveList);
            //从源头列中删除副本中剩余的列表数据
            allTimeslotRoomList.removeAll(tempList);

            tempList.clear();
            tempList = null;
            nowLeaveList.clear();
            nowLeaveList = null;
        }
//        stopWatch.stop();
//        log.info("将已经分配的教室课时分组信息列表，从源头列表删除耗时："+stopWatch.getLastTaskTimeMillis());

        */
        if (campusRoomMap != null && !campusRoomMap.isEmpty()) {
            //清空原来的列表
            allTimeslotRoomList.clear();
            //将剩余的结果设置会待分配的列表
            allTimeslotRoomList.addAll(
                    campusRoomMap.values().stream()
                            .flatMap(Collection::stream)
                            .collect(Collectors.toList()));
        }
        return allTimeslotRoomList;
    }

    /**
     * 生成待排的课表信息
     * 将教学班排课单元，转换为ClassHour课表课时对象，并添加到map，方便用来判断排课冲突以及后面的冲突计算
     *
     * @param teachingClassUnitList 剩余的，排不下课的，教学班排课单元
     * @param classHourMap          课表课时map
     */
    private ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> generateClassHourWithNoTimeslotRoom(LinkedList<TeachingClassUnit> teachingClassUnitList,
                                                                                                           ConcurrentHashMap<String, CopyOnWriteArrayList<ClassHour>> classHourMap) {
        if (teachingClassUnitList != null && !teachingClassUnitList.isEmpty()) {
            //定义列表遍历的迭代器
            Iterator iterator = teachingClassUnitList.iterator();

            while (iterator.hasNext()) {
                //获取教学班排课单元
                TeachingClassUnit teachingClassUnit = (TeachingClassUnit) iterator.next();
                //获取教学班的连上节次
                Integer connectSection = teachingClassUnit.getConnectSection();
                //按照连上节次，将教学班的排课单元拆成，一节一节的课程
                for (int i = 0; i < connectSection; i++) {
                    //创建课表课时对象
                    ClassHour classHour = new ClassHour();
                    //将教学班信息，添加到课表课时对象
                    classHour.setCourseUnit(new CourseUnit(teachingClassUnit));
                    //未分配教室课时分组信息的教学班，存入map的时候，统一存入key为“ClashesTimeslotRoom”，的列表中
                    String key = "NoTimeslotRoom";

                    //如果当前map，没有生成key
                    if (classHourMap.get(key) == null) {
                        //新建教室课时列表对象
                        CopyOnWriteArrayList<ClassHour> classHourList = new CopyOnWriteArrayList<>();
                        //将当前的课表课程对象加入列表
                        classHourList.add(classHour);
                        //将列表添加到map
                        classHourMap.put(key, classHourList);
                    } else {
                        //如果key存在，直接将课表课时对象，加入map
                        classHourMap.get(key).add(classHour);
                    }
                }

                //处理完，将对应的教学班信息移除掉；
                iterator.remove();
            }
        }

        return classHourMap;
    }
}
